﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Diagnostics;
using System.Reflection;

namespace UndisposedViewer
{
    public partial class MainForm : Form
    {
        string configFilePath;
        
        public MainForm()
        {
            InitializeComponent();
        }

        private void browseButton_Click(object sender, EventArgs e)
        {
            using (OpenFileDialog ofd = new OpenFileDialog())
            {
                if (ofd.ShowDialog(this) == DialogResult.OK)
                {
                    executablePathTextBox.Text = ofd.FileName;
                }
            }
        }

        private void goButton_Click(object sender, EventArgs e)
        {
            WriteConfigFile();

            var processStartInfo = new ProcessStartInfo(executablePathTextBox.Text, commandlineArgumentsTextBox.Text);

            processStartInfo.EnvironmentVariables["COR_ENABLE_PROFILING"] = "1";
            processStartInfo.EnvironmentVariables["COR_PROFILER"] = "{91EFB870-F9E2-4788-A47C-8F630302A10E}";
            processStartInfo.UseShellExecute = false;

            Process process = Process.Start(processStartInfo);
            process.EnableRaisingEvents = true;
            process.Exited += new EventHandler(process_Exited);

            goButton.Enabled = false;
            logfilePathTextBox.Enabled = false;
        }

        private void WriteConfigFile()
        {
            using (StreamWriter writer = new StreamWriter(configFilePath))
            {
                writer.WriteLine(executablePathTextBox.Text);
                writer.WriteLine(commandlineArgumentsTextBox.Text);
                writer.WriteLine(logfilePathTextBox.Text);
                writer.WriteLine(string.IsNullOrEmpty(timeIntervalTextBox.Text) ? -1 : int.Parse(timeIntervalTextBox.Text));
                writer.WriteLine(typeFilterTextBox.Text);
            }
        }

        void process_Exited(object sender, EventArgs e)
        {
            if (this.InvokeRequired)
            {
                this.BeginInvoke(new MethodInvoker(() => process_Exited(sender, e)));
                return;
            }

            goButton.Enabled = true;
            logfilePathTextBox.Enabled = true;

            var currentApplicationPath = Path.GetDirectoryName(Application.ExecutablePath);
            var processStartInfo = new ProcessStartInfo(
                Path.Combine(currentApplicationPath, "UndisposedLogViewer.exe"),
                "\"" + logfilePathTextBox.Text + "\"");
            Process process = Process.Start(processStartInfo);
        }

        private void MainForm_Load(object sender, EventArgs e)
        {
            InitializeConfiguration();
            TryLoadConfiguration();
        }

        private void TryLoadConfiguration()
        {
            if (File.Exists(configFilePath))
            {
                try
                {
                    using (StreamReader reader = new StreamReader(configFilePath))
                    {
                        executablePathTextBox.Text = reader.ReadLine();
                        commandlineArgumentsTextBox.Text = reader.ReadLine();
                        logfilePathTextBox.Text = reader.ReadLine();

                        int forceGCTimeInterval = Int32.Parse(reader.ReadLine());
                        forceGCCheckBox.Checked = forceGCTimeInterval != -1;
                        if (forceGCCheckBox.Checked)
                            timeIntervalTextBox.Text = forceGCTimeInterval.ToString();

                        string line = null;
                        while ((line = reader.ReadLine()) != null)
                        {
                            typeFilterTextBox.AppendText(line + Environment.NewLine);
                        }
                    }
                }
                catch (Exception e)
                {
                    MessageBox.Show("Could not load settings from config file. This usually happens when you run a new version of the application." +  Environment.NewLine +
                    "The application will launch, but the previous settings will not be shown." + Environment.NewLine + Environment.NewLine +
                    "Error : " + e.ToString(), "Undisposed - Could not load previous configuration", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                }
            }
        }

        private void InitializeConfiguration()
        {
            var appDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
            var configFileDir = System.IO.Path.Combine(appDir, "Undisposed");

            if (!Directory.Exists(configFileDir))
                Directory.CreateDirectory(configFileDir);

            configFilePath = System.IO.Path.Combine(configFileDir, "config.txt");
        }

        string originalLabel = "";
        private void findFinalizableTypesLabel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            if (string.IsNullOrEmpty(executablePathTextBox.Text))
            {
                MessageBox.Show("The executable assembly's path is required to find all finalizable types. Please enter the executable's path.");
                return;
            }

            if (finalizableTypeFinderWorker.IsBusy && !finalizableTypeFinderWorker.CancellationPending)
            {
                finalizableTypeFinderWorker.CancelAsync();
                findFinalizableTypesLabel.Text = "Cancelling...";

                return;
            }

            originalLabel = findFinalizableTypesLabel.Text;
            findFinalizableTypesLabel.Text = "Cancel";
            finalizableTypeFinderWorker.RunWorkerAsync(executablePathTextBox.Text);
        }

        private void finalizableTypeFinderWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            CreateAppDomainAndSearchForFinalizableTypes(e);
        }

        private void finalizableTypeFinderWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            typeFilterTextBox.AppendText((string)e.UserState + Environment.NewLine);
        }

        private void finalizableTypeFinderWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            findFinalizableTypesLabel.Text = originalLabel;

            if (finalizerTypeFinderAppDomain != null)
                AppDomain.Unload(finalizerTypeFinderAppDomain);

            if (e.Error != null)
            {
                MessageBox.Show(e.Error.ToString(), "Undisposed - Error finding finalizable types", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        AppDomain finalizerTypeFinderAppDomain;

        private void CreateAppDomainAndSearchForFinalizableTypes(DoWorkEventArgs e)
        {
            AppDomainSetup setup = new AppDomainSetup();
            string assemblyPath = (string)e.Argument;
            string basePath = System.IO.Path.GetDirectoryName(assemblyPath);

            setup.ApplicationBase = basePath;
            setup.PrivateBinPath = basePath;

            finalizerTypeFinderAppDomain = AppDomain.CreateDomain("UndisposedFinalizerTypeFinder", 
                null, setup);
            var remoteFinalizerTypeFinder = (FinalizerTypeFinder)
                finalizerTypeFinderAppDomain.CreateInstanceFromAndUnwrap(Assembly.GetExecutingAssembly().CodeBase, typeof(FinalizerTypeFinder).FullName);

            foreach (string typeName in remoteFinalizerTypeFinder.Find(assemblyPath, true))
                finalizableTypeFinderWorker.ReportProgress(0, typeName);
        }

        private void checkBox1_CheckedChanged(object sender, EventArgs e)
        {
            timeIntervalTextBox.Enabled = forceGCCheckBox.Checked;
        }

        private void timeIntervalTextBox_Validating(object sender, CancelEventArgs e)
        {
            int result;
            if (!int.TryParse(timeIntervalTextBox.Text, out result) || result < 0)
            {
                errorProvider.SetError(timeIntervalTextBox, "Time interval must be a positive integer");
                e.Cancel = true;
            }
            else
            {
                errorProvider.Clear();
            }
        }
    }
}
